/*
 * Copyright OpenSearch Contributors
 * SPDX-License-Identifier: Apache-2.0
 */


package org.opensearch.sql.doctest.core.builder;

import static org.opensearch.sql.doctest.core.request.SqlRequest.UrlParam;
import static org.opensearch.sql.doctest.core.request.SqlRequestFormat.IGNORE_REQUEST;
import static org.opensearch.sql.doctest.core.request.SqlRequestFormat.OPENSEARCH_DASHBOARD_REQUEST;
import static org.opensearch.sql.doctest.core.response.SqlResponseFormat.PRETTY_JSON_RESPONSE;
import static org.opensearch.sql.doctest.core.response.SqlResponseFormat.TABLE_RESPONSE;
import static org.opensearch.sql.legacy.plugin.RestSqlAction.EXPLAIN_API_ENDPOINT;
import static org.opensearch.sql.legacy.plugin.RestSqlAction.QUERY_API_ENDPOINT;
import static org.opensearch.sql.plugin.rest.RestQuerySettingsAction.LEGACY_SQL_SETTINGS_API_ENDPOINT;


import com.google.common.base.Strings;
import java.util.Arrays;
import org.opensearch.client.RestClient;
import org.opensearch.sql.doctest.core.markup.Document;
import org.opensearch.sql.doctest.core.request.SqlRequest;
import org.opensearch.sql.doctest.core.request.SqlRequestFormat;
import org.opensearch.sql.doctest.core.response.SqlResponseFormat;
import org.opensearch.sql.legacy.utils.StringUtils;

/**
 * Build document by custom DSL. To make it more readable, each doc test needs to implement this interface
 * and provide things required, such as client connection and document handle. As benefit, they can use the
 * DSL to build document in readable and fluent way.
 */
public interface DocBuilder {

  String DOCUMENT_FOLDER_ROOT = "/docs/user/";
  String IMAGE_FOLDER_PATH = DOCUMENT_FOLDER_ROOT + "img/";

  /**
   * Get client connection to cluster for sending request
   *
   * @return REST client
   */
  RestClient restClient();

  /**
   * Open document file to write
   *
   * @return document class
   */
  Document openDocument();

  default void section(String title, String description, Example... examples) {
    section(title, description, new String[0], examples);
  }

  /**
   * Entry point to start building document by DSL.
   * Each section consists of:
   * 1. Title
   * 2. Description
   * 3. Example(s)
   * 3.1 Description
   * 3.2 [Sample request]
   * 3.3 [Explain request]
   * 3.4 [Explain output]
   * 3.5 [Result set]
   *
   * @param title       title of the section
   * @param description description paragraph
   * @param examples    examples for the section
   */
  default void section(String title, String description, String[] images, Example... examples) {
    try (Document document = openDocument()) {
      document.section(title);

      if (!description.isEmpty()) {
        document.subSection("Description").paragraph(description);
      }

      if (images.length > 0) {
        document.subSection("Syntax");
        for (String image : images) {
          // Convert image name ex. "rdd/queryStatement.png" to "queryStatement" as description.
          String imageDesc = image.substring(image.lastIndexOf('/') + 1, image.lastIndexOf('.'));
          document.image("Rule ``" + imageDesc + "``", IMAGE_FOLDER_PATH + image);
        }
      }

      for (int i = 0; i < examples.length; i++) {
        String exampleTitle;
        if (examples.length > 1) {
          exampleTitle = "Example " + (i + 1);
        } else {
          exampleTitle = "Example";
        }
        if (!Strings.isNullOrEmpty(examples[i].getTitle())) {
          exampleTitle += (": " + examples[i].getTitle());
        }
        document.subSection(exampleTitle);

        Example example = examples[i];
        if (!example.getDescription().isEmpty()) {
          document.paragraph(example.getDescription());
        }

        document.codeBlock("SQL query", example.getQuery()).
            codeBlock("Explain query", example.getExplainQuery()).
            codeBlock("Explain", example.getExplainResult());

        if (example.isTable()) {
          document.table("Result set", example.getResult());
        } else {
          document.codeBlock("Result set", example.getResult());
        }
      }
    }
  }

  default Example example(String description, Requests requests) {
    return example("", description, requests);
  }

  /**
   * Construct an example by default query and explain format
   */
  default Example example(String title, String description, Requests requests) {
    return example(title, description, requests,
        queryFormat(OPENSEARCH_DASHBOARD_REQUEST, TABLE_RESPONSE),
        explainFormat(IGNORE_REQUEST, PRETTY_JSON_RESPONSE)
    );
  }

  default Example example(String description,
                          Requests requests,
                          Formats queryFormat,
                          Formats explainFormat) {
    return example("", description, requests, queryFormat, explainFormat);
  }

  default Example example(String title,
                          String description,
                          Requests requests,
                          Formats queryFormat,
                          Formats explainFormat) {
    Example example = new Example();
    example.setTitle(title);
    example.setDescription(description);
    example.setQuery(queryFormat.format(requests.query()));
    example.setTable(queryFormat.isTableFormat());
    example.setResult(queryFormat.format(requests.queryResponse()));
    example.setExplainQuery(explainFormat.format(requests.explain()));
    example.setExplainResult(explainFormat.format(requests.explainResponse()));
    return example;
  }

  /**
   * Simple method just for readability
   */
  default String title(String title) {
    return title;
  }

  default String description(String... sentences) {
    return String.join(" ", sentences);
  }

  default String[] images(String... images) {
    return images;
  }

  default Formats queryFormat(SqlRequestFormat requestFormat, SqlResponseFormat responseFormat) {
    return new Formats(requestFormat, responseFormat);
  }

  default Formats explainFormat(SqlRequestFormat requestFormat, SqlResponseFormat responseFormat) {
    return new Formats(requestFormat, responseFormat);
  }

  default Requests get(String sql) {
    Body body = new Body("\"query\":\"" + sql + "\"");
    return new Requests(
        restClient(),
        new SqlRequest("GET", QUERY_API_ENDPOINT, "", new UrlParam("sql", sql)),
        new SqlRequest("POST", EXPLAIN_API_ENDPOINT, body.toString())
    );
  }

  default Requests put(String name, Object value) {
    String setting = value == null ?
        StringUtils.format("\"%s\": {\"%s\": null}", "transient", name) :
        StringUtils.format("\"%s\": {\"%s\": \"%s\"}", "transient", name, value);
    return new Requests(
        restClient(),
        new SqlRequest("PUT", LEGACY_SQL_SETTINGS_API_ENDPOINT, new Body(setting).toString()),
        null
    );
  }

  default String multiLine(String... lines) {
    return String.join("\\n", lines);
  }

  /**
   * Query by a simple SQL is too common and deserve a dedicated overload method
   */
  default Requests post(String sql, UrlParam... params) {
    return post(new Body("\"query\":\"" + sql + "\""), params);
  }

  default Requests post(Body body, UrlParam... params) {
    String bodyStr = body.toString();
    return new Requests(
        restClient(),
        new SqlRequest("POST", QUERY_API_ENDPOINT, bodyStr, params),
        new SqlRequest("POST", EXPLAIN_API_ENDPOINT, bodyStr)
    );
  }

  default Body body(String... fieldValues) {
    return new Body(fieldValues);
  }

  default UrlParam[] params(String... keyValues) {
    return Arrays.stream(keyValues).map(UrlParam::new).toArray(UrlParam[]::new);
  }

}
